package codec

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"gedis/global"
	"github.com/panjf2000/gnet"
)

const Header string = "Gedis"
const DefaultHeaderLength = 11

/*
 * Request protocol
 * +---------------+---------+--------------+-----------+------------+-------------+-----------+--------------+-----------+
 * | Header Gedis  | version | command code |   length  | arg count  | arg1 length |    arg1   |  argN length |   argN    |
 * |    5 bytes    | 1 bytes |    1 bytes   |  4 bytes  |  1 bytes   |   4 bytes   |  N bytes  |    4 bytes   |  N bytes  |
 * +---------------+---------+--------------+-----------+------------+-------------+-----------+--------------+-----------+
 *
 * Response protocol
 * +-----------------------------+---------+--------------+-----------+------------+
 * | Result(+ success, - error)  | version | command code |   length  |    data    |
 * |          1 bytes            | 1 bytes |    1 bytes   |  4 bytes  |  N bytes   |
 * +-----------------------------+---------+--------------+-----------+------------+
 */
type RequestProtocol struct {
	Header  string
	Version int8
	Command int8
	DataLen int32
	Args    []string
}

func newRequestProtocol(version int8, command int8) *RequestProtocol {
	return &RequestProtocol{
		Version: version,
		Command: command,
	}
}

type ResponseProtocol struct {
	Result  int8
	Version int8
	Command int8
	DataLen int32
	Data    string
}

type Client struct {
	Request *RequestProtocol // This time request args.
	Addr    string           // The client addr.
}

func newClient(request *RequestProtocol, addr string) *Client {
	return &Client{
		Request: request,
		Addr:    addr,
	}
}

type GedisProtocolCodec struct{}

func NewGedisProtocolCodec() *GedisProtocolCodec {
	return &GedisProtocolCodec{}
}

// Encode the data.
func (cc *GedisProtocolCodec) Encode(c gnet.Conn, buf []byte) ([]byte, error) {
	result := make([]byte, 0)
	buffer := bytes.NewBuffer(result)

	response := c.Context().(*ResponseProtocol)

	if err := binary.Write(buffer, binary.BigEndian, response.Result); err != nil {
		s := fmt.Sprintf("Pack magin number error, %v", err)
		global.GVA_LOG.Error(s)
		return nil, errors.New(s)
	}

	if err := binary.Write(buffer, binary.BigEndian, response.Version); err != nil {
		s := fmt.Sprintf("Pack version error, %v", err)
		global.GVA_LOG.Error(s)
		return nil, errors.New(s)
	}

	if err := binary.Write(buffer, binary.BigEndian, response.Command); err != nil {
		s := fmt.Sprintf("Pack command error, %v", err)
		global.GVA_LOG.Error(s)
		return nil, errors.New(s)
	}

	if err := binary.Write(buffer, binary.BigEndian, response.DataLen); err != nil {
		s := fmt.Sprintf("Pack data length error, %v", err)
		global.GVA_LOG.Error(s)
		return nil, errors.New(s)
	}

	if err := binary.Write(buffer, binary.BigEndian, []byte(response.Data)); err != nil {
		s := fmt.Sprintf("Pack data error, %v", err)
		global.GVA_LOG.Error(s)
		return nil, errors.New(s)
	}

	return buffer.Bytes(), nil
}

// Decode the data.
func (cc *GedisProtocolCodec) Decode(c gnet.Conn) ([]byte, error) {
	if size, header := c.ReadN(DefaultHeaderLength); size == DefaultHeaderLength {
		buffer := bytes.NewBuffer(header)
		var version, command int8
		var header [5]byte
		var dataLen int32
		binary.Read(buffer, binary.BigEndian, &header)
		if string(header[:]) != Header {
			return nil, errors.New(fmt.Sprintf("[%s] it's a illegal protocol", c.RemoteAddr().String()))
		}
		binary.Read(buffer, binary.BigEndian, &version)
		binary.Read(buffer, binary.BigEndian, &command)
		binary.Read(buffer, binary.BigEndian, &dataLen)
		// parse payload
		protocolLen := DefaultHeaderLength + int(dataLen)
		if dataSize, data := c.ReadN(protocolLen); dataSize == protocolLen {
			request := newRequestProtocol(version, command)
			c.SetContext(newClient(request, c.RemoteAddr().String()))
			c.ShiftN(protocolLen) // Shift read pointer for given length.
			return data[DefaultHeaderLength:], nil
		}
		return nil, errors.New("not enough payload data")
	}
	return nil, errors.New("not enough header data")
}

// AnalyticArgs 解析真正的请求参数
func AnalyticArgs(frame []byte, request *RequestProtocol) {
	buffer := bytes.NewBuffer(frame)
	var argsCount int8
	var argCount int32
	args := make([]string, argsCount)
	binary.Read(buffer, binary.BigEndian, &argsCount)
	if argsCount > 0 {
		var i int8 = 0
		for ; i < argsCount; i++ {
			binary.Read(buffer, binary.BigEndian, &argCount)
			arg := make([]byte, argCount)
			binary.Read(buffer, binary.BigEndian, &arg)
			args = append(args, string(arg))
		}
	}
	request.Args = args
}
